Solutions 7 - Examples in Sparse + Low-Rank Splitting

Assignment 1

We need functions from the notebook L13 Sparse + Low-Rank Splitting.


In [1]:
using LinearAlgebra
# Shrinkage
function Shr(x::Array{T},τ::T) where T
    sign.(x).*max.(abs.(x).-τ,zero(T))
end

# Singular value thresholding
function D(A::Array{T},τ::T) where T
    # This can be replaced by a faster approach
    V=svd(A)
    S=Shr(V.S,τ)
    k=count(!iszero,S)
    return (V.U[:,1:k]*Diagonal(S[1:k]))*V.Vt[1:k,:]
end


Out[1]:
D (generic function with 1 method)

In [2]:
function PCPAD(A::Array{T}) where T
    # Initialize
    δ=1.0e-7
    tol=δ*norm(A)
    m,n=size(A)
    S=zero(A)
    Y=zero(A)
    L=zero(A)
    T₁=zero(A)
    μ=(m*n)/(4*(norm(A[:],1)))
    μ₁=one(T)/μ
    λ=one(T)/sqrt(max(map(T,m),n))
    λμ₁=λ*μ₁
    ν=1e20
    maxiter=1000
    iterations=0
    # Iterate
    while (ν>tol) && iterations<maxiter
        # println(iterations," ",ν)
        iterations+=1
        L.=D(A-S+μ₁.*Y,μ₁)
        S.=Shr(A-L+μ₁.*Y,λμ₁)
        T₁.=(A-L-S)
        Y.+=(μ.*T₁)
        ν=norm(T₁)
    end
    L,S, iterations
end


Out[2]:
PCPAD (generic function with 1 method)

In [3]:
# For compilation
A0=rand(3,3)


Out[3]:
3×3 Array{Float64,2}:
 0.67455   0.959578  0.614911
 0.393543  0.794696  0.646043
 0.154393  0.220087  0.870083

In [4]:
L,S,iter=PCPAD(A0)


Out[4]:
([0.4303 0.647747 0.614911; 0.393544 0.595917 0.563439; 0.153741 0.220087 0.216284], [0.244251 0.311831 0.0; -0.0 0.198779 0.0826041; 0.000651856 -0.0 0.653799], 113)

In [5]:
rank(L), L+S-A0


Out[5]:
(2, [0.0 0.0 -4.54824e-8; 5.11183e-8 1.11022e-16 0.0; 0.0 -4.87951e-8 -4.44089e-16])

In [6]:
L


Out[6]:
3×3 Array{Float64,2}:
 0.4303    0.647747  0.614911
 0.393544  0.595917  0.563439
 0.153741  0.220087  0.216284

In [7]:
# Packages for video manipulation
# import Pkg; Pkg.add("VideoIO")
# import Pkg; Pkg.add("Makie")

In [8]:
using Images
using Makie, VideoIO


┌ Info: Recompiling stale cache file C:\Users\Ivan_Slapnicar\.julia\compiled\v1.1\Makie\iZ1Bl.ji for Makie [ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a]
└ @ Base loading.jl:1184
WARNING: using GLAbstraction.update! in module GLMakie conflicts with an existing identifier.
┌ Info: Recompiling stale cache file C:\Users\Ivan_Slapnicar\.julia\compiled\v1.1\VideoIO\tZxJ7.ji for VideoIO [d6d074c3-1acf-5d4c-9a43-ef38773959a2]
└ @ Base loading.jl:1184

In [9]:
varinfo(VideoIO)


Out[9]:
name size summary
VideoIO 900.155 KiB Module
appendencode! 0 bytes typeof(appendencode!)
encode! 0 bytes typeof(encode!)
encodevideo 0 bytes typeof(encodevideo)
finishencode! 0 bytes typeof(finishencode!)
mux 0 bytes typeof(mux)
opencamera 0 bytes typeof(opencamera)
openvideo 0 bytes typeof(openvideo)
play 0 bytes typeof(play)
playvideo 0 bytes typeof(playvideo)
prepareencoder 0 bytes typeof(prepareencoder)
pump 0 bytes typeof(pump)
read 0 bytes typeof(read)
read! 0 bytes typeof(read!)
viewcam 0 bytes typeof(viewcam)

In [11]:
video=VideoIO.open("files/visor.avi")


Out[11]:
AVInput(files/visor.avi, ...), with
  1 video stream(s)

In [12]:
playvideo(video)

In [10]:
VideoIO.get_duration("files/visor.avi")


Out[10]:
229200000 microseconds

In [13]:
# Read the frames in Gray
f = VideoIO.openvideo("files/visor.avi",target_format=VideoIO.AV_PIX_FMT_GRAY8)
frames=Array{Array{Gray{Normed{UInt8,8}},2},1}(undef,0)
while !eof(f)
    push!(frames,read(f))
end
close(f)

In [14]:
# Number of frames
length(frames)


Out[14]:
2292

In [25]:
# See particular frame
frames[1401]


Out[25]:

In [18]:
# Make shorter video clip
clip=frames[1401:1450]


Out[18]:
(a vector displayed as a row to save space)

In [19]:
size(clip[1])


Out[19]:
(240, 320)

In [20]:
# Turn frames into tall matrix
mi,ni=size(clip[1])
m=mi*ni
n=length(clip)
A=Array{Float64}(undef,m,n)
for i=1:n
    A[:,i]=vec(float(clip[i]))
end

In [21]:
size(A)


Out[21]:
(76800, 50)

In [22]:
norm(A)


Out[22]:
993.2119058949775

In [24]:
# For orientation
@time Q=qr(A);
@time D(A,0.5);


  0.113372 seconds (9 allocations: 29.325 MiB, 5.80% gc time)
  0.476252 seconds (27 allocations: 146.607 MiB, 30.38% gc time)

In [22]:
# Compute the splitting - 4 minutes
@time L,S,iters=PCPAD(A)


862.221164 seconds (942.88 k allocations: 361.824 GiB, 29.64% gc time)
Out[22]:
([0.188235 0.188235 … 0.176471 0.176471; 0.188235 0.188235 … 0.176471 0.176471; … ; 0.717647 0.717647 … 0.705882 0.705882; 0.701961 0.701961 … 0.713725 0.713725], [0.0 0.0 … -0.0 -0.0; 0.0 0.0 … -0.0 -0.0; … ; 0.0 0.0 … -0.0 -0.0; -0.0 -0.0 … -0.0 -0.0], 1000)

In [24]:
# Reconstruct the low-rank video component
rank(L), norm(A-L-S), norm(A)


Out[24]:
(16, 0.0004293691494130272, 993.2119058949775)

In [25]:
svdvals(L)[1:20]


Out[25]:
20-element Array{Float64,1}:
 1000.6855882999088       
   14.872151580064315     
   11.379471858782923     
    9.729815050418251     
    7.706656281990622     
    4.156856572137867     
    3.180028236906393     
    2.456530998733223     
    1.289750460974987     
    0.7788793370744841    
    0.6411961844682933    
    0.5972429280625284    
    0.018576261768377268  
    0.008823112579057874  
    0.0005639322441757535 
    0.0002809497437104494 
    1.2373226155568438e-13
    2.740824393863828e-14 
    2.7105715728593326e-14
    2.6975866882360756e-14

In [26]:
# How to restore the video?
# First frame of the low-rank part
v1=L[:,1]
v2=reshape(v1,mi,ni)
map(Gray{Normed{UInt8,8}},v2)


Out[26]:

In [27]:
# First frame of the sparse part 
s1=S[:,1]
s2=reshape(s1,mi,ni)
map(Gray{Normed{UInt8,8}},clamp01.(s2.+0.5))


Out[27]:

In [28]:
LowRank=similar(clip)
Sparse=similar(clip)
for i=1:n
    LowRank[i]=reshape(L[:,i],mi,ni)
    Sparse[i]=clamp01.(reshape(S[:,i],mi,ni).+0.5)
end

In [29]:
# import Pkg; Pkg.add("JLD")

In [26]:
# Let us save the results
using JLD


┌ Info: Precompiling JLD [4138dd39-2aa7-5051-a626-17a0bb65d9c8]
└ @ Base loading.jl:1186

In [30]:
@save "files/visor_results.jld" LowRank Sparse

In [27]:
@load "files/visor_results.jld"


Out[27]:
2-element Array{Symbol,1}:
 :LowRank
 :Sparse 

In [29]:
LowRank[1]


Out[29]:

In [30]:
Sparse[1]


Out[30]:

In [28]:
# Play the LowRank part a bit slower (framerate=10)
props = [:priv_data => ("crf"=>"22","preset"=>"medium")]
encodevideo("files/LowRank.mp4",LowRank,framerate=10,AVCodecContextProperties=props)


┌ Info: Video file saved: C:\Users\Ivan_Slapnicar\Documents\GitHub\GIAN-Applied-NLA-Course\src\Module C - Applications/files/LowRank.mp4
└ @ VideoIO C:\Users\Ivan_Slapnicar\.julia\packages\VideoIO\1lCOP\src\encoding.jl:223
┌ Info: frame=   50 fps=0.0 q=-1.0 Lsize=      54kB time=00:00:04.70 bitrate=  94.0kbits/s speed=2.72e+03x    
└ @ VideoIO C:\Users\Ivan_Slapnicar\.julia\packages\VideoIO\1lCOP\src\encoding.jl:224
┌ Info: video:53kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 1.882648%
└ @ VideoIO C:\Users\Ivan_Slapnicar\.julia\packages\VideoIO\1lCOP\src\encoding.jl:225

In [32]:
videoLowRank=VideoIO.open("files/LowRank.mp4")
playvideo(videoLowRank)


[h264 @ 0000000023095580] Invalid NAL unit 8, skipping.

In [33]:
# Play the Sparse part
encodevideo("files/Sparse.mp4",Sparse,framerate=10,AVCodecContextProperties=props)


┌ Info: Video file saved: C:\Users\Ivan_Slapnicar\Documents\GitHub\GIAN-Applied-NLA-Course\src\Module C - Applications/files/Sparse.mp4
└ @ VideoIO C:\Users\Ivan_Slapnicar\.julia\packages\VideoIO\1lCOP\src\encoding.jl:223
┌ Info: frame=   50 fps=0.0 q=-1.0 Lsize=      97kB time=00:00:04.70 bitrate= 169.7kbits/s speed=9.48e+03x    
└ @ VideoIO C:\Users\Ivan_Slapnicar\.julia\packages\VideoIO\1lCOP\src\encoding.jl:224
┌ Info: video:96kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 1.033613%
└ @ VideoIO C:\Users\Ivan_Slapnicar\.julia\packages\VideoIO\1lCOP\src\encoding.jl:225

In [36]:
videoSparse=VideoIO.open("files/Sparse.mp4")
playvideo(videoSparse)


[h264 @ 0000000023095a00] Invalid NAL unit 8, skipping.

In [ ]: